import { CompletionRecord, CompletionRecordType, EnvironmentRecord, ExecutionContext, JSBoolean, JSNull, JSNumber, JSObject, JSString, JSUndefined, ObjectEnvironmentRecord, Realm, Reference } from './runtime';

export class Evaluator {
  private realm = new Realm();

  private globalObject = new JSObject();

  private executionContextStack = [
    new ExecutionContext(
      this.realm,
      new ObjectEnvironmentRecord(this.globalObject, null),
    ),
  ];

  constructor() {
    this.globalObject.set('log', new JSObject());
    this.globalObject.get('log').call = (args: any[]) => {
      console.log(args);
    }
  }

  evaluate(node: Record<string, any>) {
    const type = node.type;
    if ((this as any)[type]) {
      return (this as any)[type](node);
    }
  }

  evaluateModule(node: Record<string, any>) {
    const globalExecutionContext = this.executionContextStack[0];
    const newExecutionContext = new ExecutionContext(
      this.realm,
      new EnvironmentRecord(globalExecutionContext.lexicalEnvironment),
      new EnvironmentRecord(globalExecutionContext.variableEnvironment),
    );

    this.executionContextStack.push(newExecutionContext);
    let result = this.evaluate(node);
    this.executionContextStack.pop();

    return result;
  }

  Program(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  StatementList(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    const record = this.evaluate(node.children[0]);
    if (record.type === CompletionRecordType.Normal) {
      return this.evaluate(node.children[1]);
    }

    // continue 或者 break 则不继续运行
    return record;
  }

  Statement(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  BreakStatement(node: Record<string, any>) {
    return new CompletionRecord(
      CompletionRecordType.Break,
    );
  }

  ContinueStatement(node: Record<string, any>) {
    return new CompletionRecord(
      CompletionRecordType.Continue,
    );
  }

  WhileStatement(node: Record<string, any>) {
    while (true) {
      let condition = this.evaluate(node.children[2]);
      if (condition instanceof Reference) {
        condition = condition.get();
      }
  
      if (condition.toBoolean().value) {
        const record = this.evaluate(node.children[4]);
        if (record.type === CompletionRecordType.Continue) {
          continue;
        }

        if (record.type == CompletionRecordType.Break) {
          return new CompletionRecord();
        }
      } else {
        return new CompletionRecord();
      }
    }
  }

  IfStatement(node: Record<string, any>) {
    let condition = this.evaluate(node.children[2]);
    if (condition instanceof Reference) {
      condition = condition.get();
    }
    if (condition.toBoolean().value) {
      return this.evaluate(node.children[4]);
    }
  }

  ExpressionStatement(node: Record<string, any>) {
    let result = this.evaluate(node.children[0]);
    if (result instanceof Reference) {
      result = result.get();
    }
    return new CompletionRecord(
      CompletionRecordType.Normal,
      result,
    );
  }

  Expression(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  AdditiveExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    let lhs = this.evaluate(node.children[0]);
    let rhs = this.evaluate(node.children[2]);

    if (lhs instanceof Reference) {
      lhs = lhs.get();
    }

    if (rhs instanceof Reference) {
      rhs = rhs.get();
    }

    if (node.children[1].type === '+') {
      return  new JSNumber(lhs.value + rhs.value);
    }

    if (node.children[1].type === '-') {
      return new JSNumber(lhs.value - rhs.value);
    }
  }

  MultiplicativeExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    } else {
    }
  }

  PrimaryExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }
  }

  VariableDeclaration(node: Record<string, any>) {
    this.runningExecutionContext
      .lexicalEnvironment
      .set(node.children[1].value);
    return new CompletionRecord();
  }

  FunctionDeclaration(node: Record<string, any>) {
    const name = node.children[1].value;
    const code = node.children[node.children.length - 2];
    const func = new JSObject();
    (func as any).call = (args: any[]) => {
      const newExecutionContext = new ExecutionContext(
        this.realm,
        new EnvironmentRecord((func as any).environment),
        new EnvironmentRecord((func as any).environment),
      );
      this.executionContextStack.push(newExecutionContext);
      this.evaluate(code);
      this.executionContextStack.pop();
    }
    this.runningExecutionContext.lexicalEnvironment.set(name, func);
    (func as any).environment = this.runningExecutionContext.lexicalEnvironment;
    return new CompletionRecord();
  }

  Literal(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  BooleanLiteral(node: Record<string, any>) {
    if (node.value === 'true') {
      return new JSBoolean(true);
    }

    return new JSBoolean(false);
  }

  NullLiteral() {
    return new JSNull();
  }

  NumericLiteral(node: Record<string, any>) {
    let str: string = node.value;

    let radix = 10;

    if (str.match(/^0b/)) {
      str = str.substr(2);
      radix = 2;
    } else if (str.match(/^0o/)) {
      str = str.substr(2);
      radix = 8;
    } else if (str.match(/^0x/)) {
      str = str.substr(2);
      radix = 16;
    }

    let value = 0;
    for (let i = 0; i < str.length; i++) {
      let c = str.charCodeAt(i);
      if (c >= 'a'.charCodeAt(0)) {
        c = c - 'a'.charCodeAt(0) + 10;
      } else if (c >= 'A'.charCodeAt(0)) {
        c = c - 'A'.charCodeAt(0) + 10;
      } else {
        c -= '0'.charCodeAt(0);
      }

      value = value * radix + c;
    }

    return new JSNumber(value);
  }

  StringLiteral(node: Record<string, any>) {
    let unescapedString: string = node.value;
    const result: string[] = [];

    const map: Record<string, string> = {
      '\\': '\\',
      "'": "'",
      '"': '"',
      '0': '\0',
      b: '\b',
      f: '\f',
      n: '\n',
      r: '\r',
      t: '\t',
      v: '\v',
    };

    for (let i = 1; i < unescapedString.length - 1; i++) {
      if (unescapedString[i] === '\\') {
        i++;
        if (i < unescapedString.length - 1) {
          result.push(map[unescapedString[i]] ?? unescapedString[i]);
        } else {
          throw new Error('Unexpected end of string');
        }
      } else {
        result.push(unescapedString[i]);
      }
    }

    return new JSString(result);
  }

  ObjectLiteral(node: Record<string, any>) {
    const object = new JSObject();

    if (node.children.length === 2) {
      return object;
    }

    if (node.children.length === 3) {
      const object = new JSObject();

      this.PropertyList(node.children[1], object as any);

      return object;
    }
  }

  PropertyList(node: Record<string, any>, object: Map<string, any>) {
    if (node.children.length === 1) {
      this.Property(node.children[0], object);
    } else {
      this.PropertyList(node.children[0], object);
      this.Property(node.children[2], object);
    }
  }

  Property(node: Record<string, any>, object: Map<string, any>) {
    let propertyName = '';

    if (node.children[0].type === 'IdentifierName') {
      propertyName = node.children[0].value;
    } else if (node.children[0].type === 'StringLiteral') {
      propertyName = this.evaluate(node.children[0]);
    }

    object.set(propertyName, {
      value: this.evaluate(node.children[2]),
      writable: true,
      enumerable: true,
      configurable: true,
    });
  }

  AssignmentExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    const left = this.evaluate(node.children[0]);
    const right = this.evaluate(node.children[2]);

    left.set(right);
  }

  LogicalORExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    const result = this.evaluate(node.children[0]);

    if (result) {
      return result;
    }

    return this.evaluate(node.children[2]);
  }

  LogicalANDExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    const result = this.evaluate(node.children[0]);

    if (!result) {
      return result;
    }

    return this.evaluate(node.children[2]);
  }

  LeftHandSideExpression(node: Record<string, any>) {
    return this.evaluate(node.children[0]);
  }

  NewExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    if (node.children.length === 2) {
      return this.evaluate(node.children[1]).construct();

      // new 运算符的过程
      // let object = this.realm.Object.construct();

      // const classObject = this.evaluate(node.children[1]);
      // const instance = classObject.call(object);

      // if (typeof instance === 'object') {
      //   return instance;
      // } else {
      //   return object;
      // }
    }
  }

  CallExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    if (node.children.length === 2) {
      let func = this.evaluate(node.children[0]);

      if (func instanceof Reference) {
        func = func.get();
      }
      const args = this.evaluate(node.children[1]);
      return func.call(args);
    }
  }

  MemberExpression(node: Record<string, any>) {
    if (node.children.length === 1) {
      return this.evaluate(node.children[0]);
    }

    if (node.children.length === 3) {
      const obj = this.evaluate(node.children[0]).get();
      const prop = obj.get(node.children[2].value);

      if (prop.hasOwnProperty('value')) {
        return prop.value;
      }

      if (prop.hasOwnProperty('get')) {
        return prop.get().call(obj);
      }
    }
  }

  IdentifierName(node: Record<string, any>) {
    return new Reference(this.runningExecutionContext.lexicalEnvironment, node.value);
  }

  Block(node: Record<string, any>) {
    if (node.children.length === 2) {
      return;
    }

    const newExecutionContext = new ExecutionContext(
      this.runningExecutionContext.realm,
      new EnvironmentRecord(this.runningExecutionContext.lexicalEnvironment),
      this.runningExecutionContext.variableEnvironment,
    );

    this.executionContextStack.push(newExecutionContext);
    const result = this.evaluate(node.children[1]);
    this.executionContextStack.pop();

    return result;
  }

  ArgumentList(node: Record<string, any>) {
    if (node.children.length === 1) {
      let result = this.evaluate(node.children[0]);
      if (result instanceof Reference) {
        result = result.get();
      }
      return [result];
    } else {
      let result = this.evaluate(node.children[2]);
      if (result instanceof Reference) {
        result = result.get();
      }
      return this.evaluate(node.children[0]).concat(result);
    }
  }

  Arguments(node: Record<string, any>) {
    if (node.children.length === 2) {
      return [];
    }

    return this.evaluate(node.children[1]);
  }

  get runningExecutionContext() {
    return this.executionContextStack[this.executionContextStack.length - 1];
  }
}
